/********************************************************************
	created:	2011/10/22
	filename: 	Actor.cpp
	author:		Mwolf
	
	purpose:	
*********************************************************************/
#include "stdafx.h"
#include "Actor.h"
#include "Renderer/GWRenderDevice.h"
#include "GameMain.h"
#include "GameStage.h"
#include "GameDef.h"
#include "StageCollision.h"

static Actor*	ActorListHead = NULL;
static Actor*	ActorListTail = NULL;

Actor* GetFirstActor()
{
	return ActorListHead;
}

Actor* GetNextActor( Actor* actor )
{
	return actor->next;
}

void AddActorToGame( Actor* actor )
{
	if (ActorListTail == NULL)
	{
		// Build new linked list
		ActorListHead = actor;
		ActorListTail = actor;
	}
	else
	{
		// Add new actor to the tail
		ActorListTail->next = actor;
		ActorListTail = actor;
	}
}

void RemoveActorFromGame( Actor* actor )
{
	if ( actor == NULL ) return;

	Actor* actor_elem = GetFirstActor();

	if ( ActorListHead == actor )
	{
		if ( ActorListTail == ActorListHead )
		{
			// Last element, clean the list
			ActorListHead = ActorListTail = NULL;
		}
		else
		{
			ActorListHead = ActorListHead->next;
		}
		
		delete actor;

		return;
	}

	while ( actor_elem )
	{
		if ( actor_elem->next == actor )
		{
			actor_elem->next = actor->next;

			// Move tail pointer forward if tail removed
			if ( ActorListTail == actor )
				ActorListTail = actor_elem;

			delete actor;
			return;
		}

		actor_elem = GetNextActor( actor_elem );
	}
}

Actor::Actor()
: m_Position(0.0f, 0.0f),
  m_Layer(0),
  m_Velocity(Vector2::ZERO),
  m_MoveController(0.0f, 0.0f),
  next(NULL)
{
}

bool Actor::TestCollision( const BoundBox& bound, const Vector2& new_pos_rel /*= Vector2::ZERO*/ ) const
{
	BoundBox bb = m_Bound.GetTranslatedBox(m_Position + new_pos_rel);

	return bb.TestBoxCollision(bound);
}

bool Actor::TestCollisionWithCameraTilt( const BoundBox& bound ) const
{
	// Calculate bound with taking movement into consideration
	BoundBox bb = BoundBox::Merge( m_PostProjBound, m_PostProjBoundLastFrame );

	return bb.TestBoxCollision(bound);
}

bool Actor::DoCollisionMove( const BoundBox& geomBoundLastFrame,
							 const BoundBox& geomBoundThisFrame,
							 GW_INT8 depth, const Vector2& input, Vector2* output )
{
	Vector2 actorPos = Col_GetTransformedPoint( m_Position, m_Layer, KleinGame()->GetSceneTransform() );
	Vector2 newPos = actorPos + input;

	BoundBox ActorBoundThisFrame = m_Bound.GetTranslatedBox( newPos );
	BoundBox ActorBoundLastFrame = m_PostProjBoundLastFrame;

	int x_col = 0,		// x collision type -1: to the left 0: collision 1: to the right
		y_col = 0;		// y collision type -1: to the bottom 0: collision 1: to the top
	int last_x_col = 0,
		last_y_col = 0;

	// Calculate collision type for last frame
	if (ActorBoundLastFrame.xMax <= geomBoundLastFrame.xMin)
	{
		last_x_col = -1;
	}
	else if (ActorBoundLastFrame.xMin >= geomBoundLastFrame.xMax)
	{
		last_x_col = 1;
	}

	if (ActorBoundLastFrame.yMax <= geomBoundLastFrame.yMin)
	{
		last_y_col = -1;
	}
	else if (ActorBoundLastFrame.yMin >= geomBoundLastFrame.yMax)
	{
		last_y_col = 1;
	}

	// Calculate collision type for this frame
	if (ActorBoundThisFrame.xMax <= geomBoundThisFrame.xMin)
	{
		x_col = -1;
	}
	else if (ActorBoundThisFrame.xMin >= geomBoundThisFrame.xMax)
	{
		x_col = 1;
	}

	if (ActorBoundThisFrame.yMax <= geomBoundThisFrame.yMin)
	{
		y_col = -1;
	}
	else if (ActorBoundThisFrame.yMin >= geomBoundThisFrame.yMax)
	{
		y_col = 1;
	}

	if (x_col!=0 || y_col!=0)
	{
		// no collision, done
		return false;
	}
	else
	{
		*output = input;

		// check last frame with collision type
		if (last_x_col==-1)
		{
			output->x -= ActorBoundThisFrame.xMax - geomBoundThisFrame.xMin + KLEIN_COLLISION_TOLERANCE;
		}
		else if (last_x_col==1)
		{
			output->x += geomBoundThisFrame.xMax - ActorBoundThisFrame.xMin + KLEIN_COLLISION_TOLERANCE;
		}
		else if (last_y_col==-1)
		{
			output->y -= ActorBoundThisFrame.yMax - geomBoundThisFrame.yMin + KLEIN_COLLISION_TOLERANCE;
			OnHitTop();
		}
		else if (last_y_col==1)
		{
			output->y += geomBoundThisFrame.yMax - ActorBoundThisFrame.yMin + KLEIN_COLLISION_TOLERANCE;
			OnHitGround( depth );
		}
	}

	return true;
}

void Actor::Update()
{
	// Gravity
	m_Velocity += Vector2(0.0f, -0.03f);
}

void Actor::UpdatePostProjBoundWithCameraTilt( const Vector2& rel )
{
	float half_size = .5f * GetSize();

	BoundBox bound = BoundBox( -half_size, -half_size, half_size, half_size );
	Vector2 pos = Col_GetTransformedPoint( m_Position, m_Layer, KleinGame()->GetSceneTransform() );

	m_PostProjBound = bound.GetTranslatedBox( pos + rel );
}

void Actor::UpdatePostProjBound()
{
	float half_size = .5f * GetSize();

	BoundBox bound = BoundBox( -half_size, -half_size, half_size, half_size );
	Vector2 pos = Col_GetTransformedPoint( m_Position, m_Layer, KleinGame()->GetSceneTransform() );

	m_PostProjBoundLastFrame = bound.GetTranslatedBox( pos );
}

BoundBox Actor::GetTranslatedBound( const Vector2& rel )
{
	float half_size = .5f * GetSize();

	BoundBox bound = BoundBox( -half_size, -half_size, half_size, half_size );
	Vector2 pos = Col_GetTransformedPoint( m_Position, m_Layer, KleinGame()->GetSceneTransform() );

	return bound.GetTranslatedBox( pos + rel );
}

void Actor::Translate( const Vector2& vecRel )
{
	m_Position += vecRel;
}

void Actor::TranslateWithCameraTilt( const Vector2& vecRel )
{
	T3_CAMERA* game_cam = KleinGame()->GetCamera();
	Vector2 rel = vecRel;

	float angle = DEGREE( 90.f ) - T3Camera_GetTiltAnimationProgress( game_cam ) * DEGREE( 45.f );
	float factor = 1.f / sinf( angle );

	switch ( game_cam->tilt )
	{
	case T3_CAMERA_TILT_LEFT:
	case T3_CAMERA_TILT_RIGHT:
		rel.x *= factor;
		break;
	case T3_CAMERA_TILT_UP:
	case T3_CAMERA_TILT_DOWN:
		rel.y *= factor;
		break;
	default:
		break;
	}

	Translate( rel );
}

Vector2 Actor::GetPositionWithCameraTilt()
{
	return Col_GetTransformedPoint( m_Position, m_Layer, KleinGame()->GetSceneTransform() );
}

void Actor::Interactive()
{
}

void Actor::SetupMatrix()
{
	Matrix4 RotMat = KleinGame()->GetSceneRotation();
	Matrix4 TransMat = Matrix4::BuildTranslationMatrix( Vector3(m_Position, (float)m_Layer) );
	Matrix4 InvRotMat = RotMat.InverseMatrix();

	RenderSystem::SetModelMatrix( InvRotMat * TransMat * RotMat );
}

void Actor::OnHitTop()
{
	// Bumped my head, stop moving upward
	m_Velocity.y = 0.0f;
}

void Actor::OnHitGround( GW_INT8 depth )
{
	//AdjustDepthValue(depth);

	// Stop falling velocity
	m_Velocity.y = 0.0f;
}

void Actor::AdjustDepthValue( GW_INT8 depth )
{
	T3_CAMERA* game_cam = KleinGame()->GetCamera();

	// We won't change an actor's depth value until camera stops tilting
	if ( !T3Camera_IsPlayingTiltAnimation( game_cam ) &&
		T3Camera_IsOrthoProjection( game_cam ) )
	{
		GW_INT8 delta = depth - m_Layer;

		if (delta)
		{
			Vector2 offset = Vector2::ZERO;

			// Evaluate depth for actors
			switch ( game_cam->tilt )
			{
			case T3_CAMERA_TILT_NONE:
				break;
			case T3_CAMERA_TILT_LEFT:
				offset.x = delta;
				break;
			case T3_CAMERA_TILT_RIGHT:
				offset.x = -delta;
				break;
			case T3_CAMERA_TILT_UP:
				offset.y = -delta;
				break;
			case T3_CAMERA_TILT_DOWN:
				offset.y = delta;
				break;
			default:
				GW_BREAKPOINT;
				break;
			}

			m_Position += offset;

			// Move camera position as well
			// Note: This is a freaking wired way to solve bumpy movements for camera
			KleinGame()->SetDelayedCameraOffset(offset);
		}
		m_Layer = depth;
	}
}
